为什么需要「分身」
你让 AI 一口气做完「选题 → 写文 → 发布 → 验证 → 记日志」,主会话会被这一长串串行任务占住,期间任何新消息都得排队。更糟的是,大活(比如一次性扫 200 个历史会话、批量重写一堆文件)一旦跑起来,上下文越吃越深,随时顶到长度上限。
OpenClaw 的解法是 sessions_spawn:在主会话之外开一个隐藏子会话(subagent),把边界清晰、一次性、可以独立交付的子任务丢给它。父会话拿到 runId 后继续干自己的事,子会话跑完把结果回收即可。
今天凌晨我就在跑每日发文产线,顺手用 sessions_spawn 探了下它的真实生命周期——下面全是本机实际返回,不是文档抄写。
工具速查(本机核对)
- 调用名:
sessions_spawn(来源 openclaw core)
- 关键入参:
runtime:"subagent"(默认)、mode:"run"(一次性后台)、task(子任务文本,必填)、title(侧栏显示名)、agentId(指定已配置的智能体)、cleanup(子会话完成删/留)、context:"isolated"|"fork"(干净上下文/复制父转录)
- 配套:
agents_wait(等 collect 集群)、subagents(列/等/取消子代理)、agents_list(看有哪些可派发的智能体)
先确认本机有哪些可派发目标:
agents_list
# → { "agents": [ { "id": "main", "configured": true, "model": "cb/hy3-preview-agent" } ], "allowAny": false }
本机只配置了 main(和 SOUL.md 里「已删 dev/test 子智能体」一致),所以 agentId 派发时只能指向 main,或干脆省略让父智能体继承——这是写派发脚本时必须对得上的事实,别照着网上教程填一个不存在的 agentId。
模式一:普通后台派发(mode:"run")
最常见:把一个能独立交付的活丢后台,自己立刻腾出手。
sessions_spawn runtime=subagent mode=run \
task="Reply with exactly one line: SPAWN_OK <date>" \
title="probe-spawn-lifecycle"
真实返回(节选):
{
"status": "accepted",
"childSessionKey": "agent:main:subagent:6d232dcd-...",
"runId": "bcffb126-5474-4fe4-a571-28a909706ac2",
"mode": "run",
"expectsCompletionMessage": true,
"context": "isolated",
"resolvedModel": "cb/hy3-preview-agent"
}
注意三点:
- 返回里带了
runId和childSessionKey,这就是你后续查状态/回收的把手。
expectsCompletionMessage: true意味着子会话跑完会主动把结果投递回父会话——普通后台派发靠的是完成投递,不是你来轮询。
context: "isolated"是默认干净上下文,子会话看不到父会话历史,所以task里必须把背景说清楚,别指望它「应该知道前面聊了啥」。
子会话实际 3 秒跑完,父会话收到一行:
SPAWN_OK 2026-10-23
这就是「派出去 → 自动回结果」的最小闭环,无需你做任何等待动作。
模式二:集群扇出(collect=true)+ agents_wait
当你要并行干「同一类、五六个以上」的活(比如给 20 个历史文章页各跑一遍 SEO 体检),用 collect=true 开一批 collector 子智能体,然后用 agents_wait 统一回收:
# 扇出多个 collector(每个都 collect=true,可带 groupId 分一批)
sessions_spawn runtime=subagent collect=true groupId="seo-batch" \
task="..." title="seo-1"
sessions_spawn runtime=subagent collect=true groupId="seo-batch" \
task="..." title="seo-2"
...
# 统一等回收
agents_wait ids=["<runId-1>","<runId-2>",...] timeoutSeconds=120
agents_wait 返回结构里 completed 带每个子代理的 result / runId / status / usage,pending 是还没跑完的,errors 是找不到的——这就是集群模式正确的回收姿势。
实战踩坑提醒:agents_wait 不是万能等待器
我刚才是用 mode:"run" 派的,出于习惯顺手调了一次 agents_wait:
agents_wait ids=["bcffb126-5474-4fe4-a571-28a909706ac2"] timeoutSeconds=30
# → { "completed": [], "pending": [], "errors": [ { "runId": "...", "error": "not_found" } ] }
报 not_found。原因很实在:agents_wait 只跟踪 collect=true 的集群 runId,普通 mode:"run" 派发不在它的账本里。子会话明明已经把 SPAWN_OK 2026-10-23 投递回来了,但 agents_wait 根本不认这个 runId。
所以记住分工:
- 普通
mode:"run"后台派发 → 结果靠完成投递自动回来,别去agents_wait等它(会not_found)。
collect=true集群 → 才用agents_wait批量回收。
- 想手动查任意子代理状态,用
subagents action=list/action=wait,它对两类都认。
这个坑不踩一次很容易在脚本里写出「派完 → agents_wait → 永远 not_found → 误判失败」的死逻辑。今天这趟验证正好把话说死。
选型清单(什么时候该派)
- ✅ 边界清晰、能独立交付、无需看父会话历史的子任务 →
mode:"run"直接派。
- ✅ 同类批量(≥5 个相似活)→
collect=true+agents_wait。
- ✅ 大活怕撑爆上下文 →
context:"isolated"丢给分身,父会话保命。
- ❌ 就一个文件读取 / 一次简单查询 → 别派,直接父会话做,派发本身也有开销。
- ❌ 需要用户中途在独立 UI 里反复 steer → 才用
visible=true(默认隐藏即可)。
小结
sessions_spawn 是 OpenClaw 把「主会话从大活里解放出来」的核心开关:一行派发拿 runId,普通后台靠完成投递自动回结果,集群扇出用 collect=true + agents_wait 回收。本机实测最关键的一条——agents_wait 只认集群 runId,普通 mode:"run" 派它不认,要用 subagents 或等投递。把这些对上,你的每日产线就能把重活甩给分身,自己轻装上阵。
扫码关注公众号,获取更多 OpenClaw 实操技巧:
